package com.tony.jrp;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.tony.jrp.config.JRPConfig;
import com.tony.jrp.config.JRPProxy;
import com.tony.jrp.config.ProxyType;
import com.tony.jrp.core.ReverseProxyVerticle;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.*;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServer;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.jackson.DatabindCodec;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 反向代理服务启动器
 */
public class JRPServerVerticle extends AbstractVerticle {
    private static final Logger logger = LoggerFactory.getLogger(JRPServerVerticle.class);
    public static final String DEFAULT_CONFIG_PATH;


    @Data
    static class JrpInfo {
        private JsonObject json;
        private JRPConfig config;

    }

    /**
     * 配置信息
     */
    JrpInfo jrpInfo = new JrpInfo();
    /**
     * 前端配置管理端口服务。
     */
    HttpServer httpServer;
    /**
     * 所有已经部署的代理服务,key：代理部署id，代理实体类
     */
    Set<ReverseProxyVerticle> deployProxySet = new HashSet<>();

    static {
        DEFAULT_CONFIG_PATH = "conf" + File.separator + "config.json";
    }

    /**
     * 开发测试用
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        Vertx.vertx().deployVerticle(new JRPServerVerticle());
    }

    /**
     * 获取默认配置文件路径
     *
     * @return 配置文件路径
     */
    private String getDefaultConfigPath() {
        String value = System.getenv("VERTX_CONFIG_PATH");
        if (value == null || value.trim().isEmpty()) {
            value = System.getProperty("vertx-config-path");
        }
        if (value != null && !value.trim().isEmpty()) {
            return value.trim();
        } else {
            File file = ((ContextInternal) vertx.getOrCreateContext()).owner().resolveFile(DEFAULT_CONFIG_PATH);
            boolean exists = file != null && file.exists();
            if (!exists) {
                // 从JAR根目录读取（路径以 / 开头）
                InputStream inputStream = JRPServerVerticle.class.getResourceAsStream(File.separator + DEFAULT_CONFIG_PATH);
                //从当前类所在包的相对路径读取
                //InputStream inputStream = MyClass.class.getResourceAsStream("config.properties");
                assert inputStream != null;
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
                try {
                    bufferedReader.close();
                    inputStream.close();
                    String result = bufferedReader.lines().collect(Collectors.joining("\n"));
                    vertx.fileSystem().writeFile(DEFAULT_CONFIG_PATH, Buffer.buffer(result))
                            .onSuccess(h -> logger.warn("创建默认配置文件[{}]成功！", DEFAULT_CONFIG_PATH))
                            .onFailure(er -> logger.warn("创建默认配置保存失败：{}！", er.getMessage(), er));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return exists ? file.getAbsolutePath() : null;
        }
    }

    @Override
    public void start(Promise<Void> startPromise) {
        //System.out.println("resourcePath:" + getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
        System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory");
        DatabindCodec.mapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        String configPath = getJarOrClassesPath() + DEFAULT_CONFIG_PATH;
        initConfigFile(configPath).onSuccess(handler -> {
            ConfigStoreOptions fileStore = new ConfigStoreOptions()
                    .setType("file")
                    .setConfig(new JsonObject().put("path", configPath));
            ConfigRetriever retriever = ConfigRetriever.create(vertx, new ConfigRetrieverOptions().addStore(fileStore));
            retriever.getConfig().onComplete(json -> {
                if (json.succeeded()) {
                    JsonObject result = json.result();
                    String jsonStr = result.toString();
                    JRPConfig config = Json.decodeValue(jsonStr, JRPConfig.class);
                    jrpInfo.setJson(result);
                    jrpInfo.setConfig(config);
                    startServer(startPromise, config);
                } else {
                    logger.error("JRP server start error:{}", json.cause().getMessage(), json.cause());
                }
            });

            retriever.listen(change -> {
                JsonObject newJson = change.getNewConfiguration();
                String jsonStr = newJson.toString();
                if (!jsonStr.equals(jrpInfo.getJson().toString())) {
                    JRPConfig config = Json.decodeValue(jsonStr, JRPConfig.class);
                    updateServer(config).onSuccess(h -> {
                        jrpInfo.setJson(newJson);
                        jrpInfo.setConfig(config);
                    }).onFailure(e -> {
                        logger.error("配置重新加载异常：{}", e.getMessage(), e);
                        logger.warn("开始还原配置为：{}", jrpInfo.getJson());
                        String defaultConfigPath = getDefaultConfigPath();
                        logger.info("配置文件路径为：{}", defaultConfigPath);
                        if (defaultConfigPath != null) {
                            vertx.fileSystem().writeFile(defaultConfigPath, jrpInfo.getJson().toBuffer()).onSuccess(h -> logger.warn("还原配置保存成功！")).onFailure(er -> logger.warn("还原配置保存失败：{}！", er.getMessage(), e));
                        }
                    });
                }
            });
        });
    }

    private ClassLoader getClassLoader() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = getClass().getClassLoader();
        }
        // when running on substratevm (graal) the access to class loaders
        // is very limited and might be only available from compile time
        // known classes. (Object is always known, so we do a final attempt
        // to get it here).
        if (cl == null) {
            cl = Object.class.getClassLoader();
        }
        return cl;
    }

    /**
     * 初始化配置文件
     *
     * @param configPath 配置文件路径
     */
    private Future<Void> initConfigFile(String configPath) {
        Promise<Void> promise = Promise.promise();
        File file = new File(configPath);
        if (!file.exists()) {
            file = file.getParentFile();
            while (!file.exists()) {
                boolean mkdir = file.mkdir();
                if (mkdir) {
                    file = file.getParentFile();
                }
            }
            // 从JAR根目录读取（路径以 / 开头）
            String name = "conf/config.json";
//            URL url = Loader.getResource(filename, myClassLoader);
            InputStream inputStream = getClassLoader().getResourceAsStream(name);
            //从当前类所在包的相对路径读取
            //InputStream inputStream = MyClass.class.getResourceAsStream("config.properties");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            try {
                String result = bufferedReader.lines().collect(Collectors.joining("\n"));
                bufferedReader.close();
                inputStream.close();
                vertx.fileSystem().writeFile(configPath, Buffer.buffer(result))
                        .onSuccess(h -> {
                            logger.warn("初始化默认配置文件[{}]成功！", configPath);
                            promise.complete();
                        })
                        .onFailure(er -> {
                            logger.error("初始化默认配置保存失败：{}！", er.getMessage(), er);
                            promise.complete();
                        });
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            promise.complete();
        }
        return promise.future();
    }

    private String getJarOrClassesPath() {
        String path = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
        File file = new File(path);
        return file.isDirectory() ? path : (file.getParent() + File.separator);
    }

    /**
     * 更新代理
     *
     * @param newConfig 最新配置信息
     */
    private synchronized Future<Void> updateServer(JRPConfig newConfig) {
        Promise<Void> promise = Promise.promise();
        JRPConfig oldConfig = jrpInfo.getConfig();
        List<Future> futureList = new ArrayList<>(deployProxySet.size());
        if (oldConfig.getPort().equals(newConfig.getPort())) {
            Map<String, ReverseProxyVerticle> deployMap = deployProxySet.stream().collect(Collectors.toMap(r -> r.getProxy().key(), r -> r));
            Map<String, JRPProxy> newProxyMap = newConfig.getProxies().stream().collect(Collectors.toMap(JRPProxy::key, r -> r));
            //处理需要删除的
            deployProxySet.stream().filter(r -> !newProxyMap.containsKey(r.key())).forEach(delete ->
                    futureList.add(vertx.undeploy(delete.deploymentID()))
            );
            //服务配置管理端口没变，热加载
            for (JRPProxy proxy : newConfig.getProxies()) {
                final ReverseProxyVerticle proxyVerticle;
                if (deployMap.containsKey(proxy.key())) {
                    if (proxy.getType() == ProxyType.HTTP || proxy.getType() == ProxyType.HTTPS) {
                        //已存在https/http，需要热加载
                        proxyVerticle = deployMap.get(proxy.key());
                        futureList.add(proxyVerticle.updateRoute(proxy));
                    } else {
                        proxyVerticle = deployMap.get(proxy.key());
                        proxyVerticle.updateProxy(proxy);
                    }
                } else {
                    //不存在新增
                    proxyVerticle = new ReverseProxyVerticle(proxy);
                    futureList.add(vertx.deployVerticle(proxyVerticle)
                            .onSuccess(deploymentID ->
                                    deployProxySet.add(proxyVerticle))
                            .onFailure(e -> logger.error(" Proxy [{}] failed:{}.", proxy.key(), e.getMessage(), e)));
                }
            }
            if (!futureList.isEmpty()) {
                CompositeFuture.all(futureList).onSuccess(future -> {
                    logger.info("Reverse proxies update successfully.");
                    promise.complete();
                }).onFailure(ex -> {
                    logger.error("At least one reverse proxy failed:{}，rollback to the previous config.", ex.getMessage(), ex);
                    promise.fail(ex);
                });
            } else {
                promise.complete();
            }
        } else {
            //配置页面端口变了，全部重新加载
            httpServer.close();
            deployProxySet.forEach(proxy -> futureList.add(vertx.undeploy(proxy.deploymentID())));
            deployProxySet.clear();
            CompositeFuture.all(futureList).onSuccess(future -> {
                logger.info("All reverseProxy stopped successfully.");
                Promise<Void> reStartPromise = Promise.promise();
                startServer(reStartPromise, newConfig);
                reStartPromise.future().onSuccess(success -> promise.complete()).onFailure(promise::fail);
            }).onFailure(ex -> {
                logger.error("At least one reverseProxy failed:{}.", ex.getMessage(), ex);
                promise.fail(ex);
            });
        }
        return promise.future();
    }

    /**
     * 启动代理服务
     *
     * @param startPromise 启动结果
     * @param config       配置信息
     */
    private void startServer(Promise<Void> startPromise, JRPConfig config) {
        Integer port = config.getPort();
        httpServer = vertx.createHttpServer();
        httpServer.requestHandler(req -> req.response()
                .putHeader("content-type", "text/plain")
                .end("Welcome to use JRP!")).listen(port).onComplete(http -> {
            if (http.succeeded()) {
                logger.info("JRP server started on port {}!", port);
                List<Future> futureList = new ArrayList<>(config.getProxies().size());
                for (JRPProxy proxy : config.getProxies()) {
                    ReverseProxyVerticle proxyVerticle = new ReverseProxyVerticle(proxy);
                    futureList.add(vertx.deployVerticle(proxyVerticle)
                            .onSuccess(deploymentID -> deployProxySet.add(proxyVerticle))
                            .onFailure(e -> logger.error("proxy [{}] failed", proxy.toString())));
                }
                if (!futureList.isEmpty()) {
                    CompositeFuture.all(futureList).onSuccess(future -> {
                        logger.info("All reverse proxies start successfully.");
                        startPromise.complete();
                    }).onFailure(ex -> {
                        logger.error("At least one reverse proxy failed:{}.", ex.getMessage(), ex);
                        deployProxySet.forEach(r -> vertx.undeploy(r.deploymentID()));
                        deployProxySet.clear();
                        startPromise.fail(ex);
                    });
                } else {
                    logger.warn("No proxy in config!");
                    deployProxySet.forEach(r -> vertx.undeploy(r.deploymentID()));
                    deployProxySet.clear();
                    startPromise.complete();
                }
            } else {
                logger.error("JRP server start on port {} error:{}", port, http.cause().getMessage(), http.cause());
                startPromise.fail(http.cause());
            }
        });
    }
}
